/*
 ------------------------------------------------------------------------------
 Copyright (C) 2009-2011 Eternal Games.

 This file is part of the Eternal Tech source code.

 The Eternal Tech source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The Eternal Tech source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the Eternal Tech source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


//
// win_main.cpp - ...
//


#include "win_local.h"


static cvar_t				*sys_osVersion;
static cvar_t				*sys_processor;
static cvar_t				*sys_memory;
static cvar_t				*sys_videoCard;
static cvar_t				*sys_soundCard;
static cvar_t				*sys_userName;


#define MAX_LIST_FILES				65536


/*
 ==================
 Sys_SortFileList
 ==================
*/
static int Sys_SortFileList (const void *elem1, const void *elem2){

	const char	*name1 = *(const char **)elem1;
	const char	*name2 = *(const char **)elem2;

	return Str_Compare(name1, name2);
}

/*
 ==================
 Sys_AddFileToList
 ==================
*/
static int Sys_AddFileToList (const char *name, const char **files, int fileCount){

	if (fileCount == MAX_LIST_FILES)
		return fileCount;	// Too many files

	// Add it
	files[fileCount++] = Mem_DupString(name, com_genericMemoryPool);

	return fileCount;
}

/*
 ==================
 Sys_ListFiles
 ==================
*/
const char **Sys_ListFiles (const char *directory, const char *extension, bool sort, int *numFiles){

	WIN32_FIND_DATA	findInfo;
	HANDLE			findHandle;
	BOOL			findResult = TRUE;
	bool			listDirectories, listFiles;
	char			name[MAX_PATH_LENGTH];
	const char		**fileList;
	const char		*files[MAX_LIST_FILES];
	int				fileCount = 0;
	int				i;

	if (extension != NULL && !Str_Compare(extension, "/")){
		listDirectories = true;
		listFiles = false;
	}
	else {
		listDirectories = false;
		listFiles = true;
	}

	Str_SPrintf(name, sizeof(name), "%s/*", directory);

	// Find the first file
	findHandle = FindFirstFile(name, &findInfo);
	if (findHandle == INVALID_HANDLE_VALUE){
		*numFiles = 0;
		return NULL;
	}

	while (findResult == TRUE){
		// Check for invalid file name
		if (!Str_Compare(findInfo.cFileName, ".") || !Str_Compare(findInfo.cFileName, "..")){
			findResult = FindNextFile(findHandle, &findInfo);
			continue;
		}

		// Ignore certain directories
		if (findInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY){
			if (!Str_ICompare(findInfo.cFileName, CODE_DIRECTORY) || !Str_ICompare(findInfo.cFileName, DOCS_DIRECTORY) || !Str_ICompare(findInfo.cFileName, SVN_DIRECTORY)){
				findResult = FindNextFile(findHandle, &findInfo);
				continue;
			}
		}

		// Add it
		if (findInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY){
			if (listDirectories)
				fileCount = Sys_AddFileToList(findInfo.cFileName, files, fileCount);
		}
		else {
			if (listFiles){
				if (extension){
					Str_ExtractFileExtension(findInfo.cFileName, name, sizeof(name));
					if (!Str_ICompare(extension, name))
						fileCount = Sys_AddFileToList(findInfo.cFileName, files, fileCount);
				}
				else
					fileCount = Sys_AddFileToList(findInfo.cFileName, files, fileCount);
			}
		}

		// Find the next file
		findResult = FindNextFile(findHandle, &findInfo);
	}

	FindClose(findHandle);

	if (!fileCount){
		*numFiles = 0;
		return NULL;
	}

	// Sort the list if desired
	if (sort)
		qsort(files, fileCount, sizeof(char *), Sys_SortFileList);

	// Copy the list
	fileList = (const char **)Mem_Alloc((fileCount + 1) * sizeof(char *), com_genericMemoryPool);

	for (i = 0; i < fileCount; i++)
		fileList[i] = files[i];

	fileList[i] = NULL;

	*numFiles = fileCount;

	return fileList;
}

void Sys_FreeFileList (const char **fileList){

	int		i;

	if (!fileList)
		return;

	for (i = 0; fileList[i]; i++)
		Mem_Free(fileList[i]);

	Mem_Free(fileList);
}

const char *Sys_DefaultBaseDirectory (){

	return winMain.currentDirectory;
}

void Sys_CreateDirectory (const char *directory){

	CreateDirectory(directory, NULL);
}

/*
 ==================
 Win_IsWindowFullscreen
 ==================
*/
bool Win_IsWindowFullscreen ()
{
	if (!winMain.hWndMain || !winMain.isFullscreen)
		return false;

	return true;
}

/*
 ==================
 Win_SetEditorWindow
 ==================
*/
void Win_SetEditorWindow (void *wndHandle)
{
	winMain.hWndEditor = (HWND)wndHandle;
}

/*
 ==============================================================================

    CLIPBOARD TEXT

 ==============================================================================
*/


/*
 ==================
 Win_GetClipboardText
 ==================
*/
const char *Win_GetClipboardText ()
{
	HANDLE		 hClipboardData;
	const char   *data, *text;

	if (!IsClipboardFormatAvailable(CF_TEXT))
		return NULL;

	if (!OpenClipboard(NULL))
		return NULL;

	hClipboardData = GetClipboardData(CF_TEXT);
	if (!hClipboardData)
	{
		CloseClipboard();
		return NULL;
	}

	data = (const char *)GlobalLock(hClipboardData);
	if (!data)
	{
		CloseClipboard();
		return NULL;
	}

	text = Mem_DupString(data, winMain.systemMemoryPool);
	GlobalUnlock(hClipboardData);

	CloseClipboard();

	return text;
}


/*
 ==============================================================================

    PROCESSOR DETECTION

 ==============================================================================
*/


/*
 ==================
 Win_DetectProcessor
 ==================
*/
static void Win_DetectProcessor ()
{
	SYSTEM_INFO   systemInfo;
	char		  cpuVendor[16];
	int			  cpuInfo[4];
	int			  stdBits[4], extBits[4];

	// Get the CPU vendor string
	__cpuid(cpuInfo, 0x00000000);

	((int *)cpuVendor)[0] = cpuInfo[1];
	((int *)cpuVendor)[1] = cpuInfo[3];
	((int *)cpuVendor)[2] = cpuInfo[2];
	((int *)cpuVendor)[3] = 0;

	// Get the standard feature bits
	__cpuid(stdBits, 0x00000001);

	// Get the extended feature bits
	__cpuid(extBits, 0x80000001);

	// Set the CPU id
	if (!Str_Compare(cpuVendor, "AuthenticAMD"))
		winMain.cpuId = CPUID_AMD;
	else if (!Str_Compare(cpuVendor, "GenuineIntel"))
		winMain.cpuId = CPUID_INTEL;
	else
		winMain.cpuId = CPUID_GENERIC;

	if (stdBits[3] & BIT(23))
		winMain.cpuId |= CPUID_MMX;
	if (extBits[3] & BIT(22))
		winMain.cpuId |= CPUID_MMX_EXT;
	if (extBits[3] & BIT(31))
		winMain.cpuId |= CPUID_3DNOW;
	if (extBits[3] & BIT(30))
		winMain.cpuId |= CPUID_3DNOW_EXT;
	if (stdBits[3] & BIT(25))
		winMain.cpuId |= CPUID_SSE;
	if (stdBits[3] & BIT(26))
		winMain.cpuId |= CPUID_SSE2;
	if (stdBits[2] & BIT(0))
		winMain.cpuId |= CPUID_SSE3;
	if (stdBits[2] & BIT(9))
		winMain.cpuId |= CPUID_SSSE3;
	if (stdBits[2] & BIT(19))
		winMain.cpuId |= CPUID_SSE41;
	if (stdBits[2] & BIT(20))
		winMain.cpuId |= CPUID_SSE42;
	if (extBits[2] & BIT(6))
		winMain.cpuId |= CPUID_SSE4A;
	if (extBits[2] & BIT(11))
		winMain.cpuId |= CPUID_XOP;
	if (extBits[2] & BIT(16))
		winMain.cpuId |= CPUID_FMA4;
	if (stdBits[2] & BIT(28))
		winMain.cpuId |= CPUID_AVX;

	// Set the CPU count
	GetSystemInfo(&systemInfo);

	winMain.cpuCount = systemInfo.dwNumberOfProcessors;

	// Set the CPU string
	if (winMain.cpuId & CPUID_AMD)
		Str_Copy(winMain.cpuString, "AMD", sizeof(winMain.cpuString));
	else if (winMain.cpuId & CPUID_INTEL)
		Str_Copy(winMain.cpuString, "Intel", sizeof(winMain.cpuString));
	else
		Str_Copy(winMain.cpuString, "Generic", sizeof(winMain.cpuString));

	if (winMain.cpuId & CPUID_MMX)
	{
		Str_Append(winMain.cpuString, " w/ MMX", sizeof(winMain.cpuString));

		if (winMain.cpuId & CPUID_MMX_EXT)
			Str_Append(winMain.cpuString, "+", sizeof(winMain.cpuString));

		if (winMain.cpuId & CPUID_3DNOW)
		{
			Str_Append(winMain.cpuString, " & 3DNow", sizeof(winMain.cpuString));

			if (winMain.cpuId & CPUID_3DNOW_EXT)
				Str_Append(winMain.cpuString, "+", sizeof(winMain.cpuString));
		}

		if (winMain.cpuId & CPUID_SSE)
			Str_Append(winMain.cpuString, " & SSE", sizeof(winMain.cpuString));
		if (winMain.cpuId & CPUID_SSE2)
			Str_Append(winMain.cpuString, " & SSE2", sizeof(winMain.cpuString));
		if (winMain.cpuId & (CPUID_SSE3 | CPUID_SSSE3))
			Str_Append(winMain.cpuString, " & SSE3", sizeof(winMain.cpuString));
		if (winMain.cpuId & (CPUID_SSE41 | CPUID_SSE42 | CPUID_SSE4A))
			Str_Append(winMain.cpuString, " & SSE4", sizeof(winMain.cpuString));

		if (winMain.cpuId & CPUID_XOP)
			Str_Append(winMain.cpuString, " & XOP", sizeof(winMain.cpuString));
		if (winMain.cpuId & CPUID_FMA4)
			Str_Append(winMain.cpuString, " & FMA4", sizeof(winMain.cpuString));

		if (winMain.cpuId & CPUID_AVX)
			Str_Append(winMain.cpuString, " & AVX", sizeof(winMain.cpuString));
	}

	Str_Append(winMain.cpuString, Str_VarArgs(" (%i CPU%s)", winMain.cpuCount, (winMain.cpuCount != 1) ? "s" : ""), sizeof(winMain.cpuString));
}

/*
 ==================
 Win_GetProcessorId
 ==================
*/
int Win_GetProcessorId ()
{
	return winMain.cpuId;
}

/*
 ==================
 Win_GetProcessorCount
 ==================
*/
int Win_GetProcessorCount ()
{
	return winMain.cpuCount;
}

/*
 ==================
 Win_GetProcessorString
 ==================
*/
const char *Win_GetProcessorString ()
{
	return winMain.cpuString;
}


/*
 ==============================================================================

    VIDEO AND SOUND CARD DETECTION

 ==============================================================================
*/


/*
 ==================
 Win_GetVideoCard
 ==================
*/
static void Win_GetVideoCard (char *string, int maxLength)
{
	DISPLAY_DEVICE	displayDevice;
	HKEY			hKey, hKeyCheck;
	char			path[MAX_STRING_LENGTH];
	ulonglong		memorySizeEx;
	ulong			memorySize;
	int				size, max;
	int				i;

	displayDevice.cb = sizeof(DISPLAY_DEVICE);

	if (!EnumDisplayDevices(NULL, 0, &displayDevice, 0))
	{
		Str_Copy(string, "<UNKNOWN VIDEO CARD>", maxLength);
		return;
	}

	Str_Copy(string, displayDevice.DeviceString, maxLength);

	// Try to get memory size information from the registry
	if (!Str_ICompareChars(displayDevice.DeviceKey, "\\Registry\\Machine\\", 18))
	{
		if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, displayDevice.DeviceKey + 18, 0, KEY_READ, &hKeyCheck) != ERROR_SUCCESS)
			return;

		size = sizeof(memorySizeEx);

		if (RegQueryValueEx(hKeyCheck, "HardwareInformation.qwMemorySize", NULL, NULL, (LPBYTE)&memorySizeEx, (LPDWORD)&size) != ERROR_SUCCESS)
		{
			size = sizeof(memorySize);

			if (RegQueryValueEx(hKeyCheck, "HardwareInformation.MemorySize", NULL, NULL, (LPBYTE)&memorySize, (LPDWORD)&size) != ERROR_SUCCESS)
			{
				RegCloseKey(hKeyCheck);
				return;
			}

			memorySizeEx = memorySize;
		}

		RegCloseKey(hKeyCheck);
	}
	else
	{
		if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, "HARDWARE\\DEVICEMAP\\VIDEO", 0, KEY_READ, &hKey) != ERROR_SUCCESS)
			return;

		size = sizeof(max);

		if (RegQueryValueEx(hKey, "MaxObjectNumber", NULL, NULL, (LPBYTE)&max, (LPDWORD)&size) != ERROR_SUCCESS)
		{
			RegCloseKey(hKey);
			return;
		}

		for (i = 0; i <= max; i++)
		{
			size = sizeof(path) - 1;

			if (RegQueryValueEx(hKey, Str_VarArgs("\\Device\\Video%i", i), NULL, NULL, (LPBYTE)path, (LPDWORD)&size) != ERROR_SUCCESS)
				continue;

			if (Str_ICompareChars(path, "\\Registry\\Machine\\", 18))
			{
				if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, path, 0, KEY_READ, &hKeyCheck) != ERROR_SUCCESS)
					continue;
			}
			else
			{
				if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, path + 18, 0, KEY_READ, &hKeyCheck) != ERROR_SUCCESS)
					continue;
			}

			size = sizeof(memorySizeEx);

			if (RegQueryValueEx(hKeyCheck, "HardwareInformation.qwMemorySize", NULL, NULL, (LPBYTE)&memorySizeEx, (LPDWORD)&size) != ERROR_SUCCESS)
			{
				size = sizeof(memorySize);

				if (RegQueryValueEx(hKeyCheck, "HardwareInformation.MemorySize", NULL, NULL, (LPBYTE)&memorySize, (LPDWORD)&size) != ERROR_SUCCESS)
				{
					RegCloseKey(hKeyCheck);
					continue;
				}

				memorySizeEx = memorySize;
			}

			RegCloseKey(hKeyCheck);

			break;
		}

		RegCloseKey(hKey);

		if (i > max)
			return;
	}

	Str_SPrintf(string, maxLength, "%s w/ %I64u MB", displayDevice.DeviceString, memorySizeEx >> 20);
}

/*
 ==================
 Win_GetSoundCard
 ==================
*/
static void Win_GetSoundCard (char *string, int maxLength)
{
	WAVEOUTCAPS	waveOutCaps;

	if (waveOutGetDevCaps(0, &waveOutCaps, sizeof(WAVEOUTCAPS)) != MMSYSERR_NOERROR)
	{
		Str_Copy(string, "<UNKNOWN SOUND CARD>", maxLength);
		return;
	}

	Str_Copy(string, waveOutCaps.szPname, maxLength);
}


/*
 ==============================================================================

    INITIALIZATION AND SHUTDOWN

 ==============================================================================
*/


/*
 ==================
 Win_InitSystem

 TODO Remove multimedia and high-resolution performance timer?, they are only
 used for performance testing
 ==================
*/
void Win_InitSystem ()
{
	OSVERSIONINFOEX   osVersionInfo;
	MEMORYSTATUSEX	  memoryStatus;
	char			  string[MAX_STRING_LENGTH];
	int				  length;

	Com_Printf("-------- System Initialization --------\n");

	// Get OS version info
	osVersionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);

	if (!GetVersionEx((OSVERSIONINFO *)&osVersionInfo))
		Com_Error(true, "Couldn't get OS version info");

	if (osVersionInfo.dwPlatformId != VER_PLATFORM_WIN32_NT)
		Com_Error(ERR_FATAL, ENGINE_NAME " requires Windows XP or later");
	if (osVersionInfo.dwMajorVersion < 5 || (osVersionInfo.dwMajorVersion == 5 && osVersionInfo.dwMinorVersion < 1))
		Com_Error(ERR_FATAL, ENGINE_NAME " requires Windows XP or later");

	if (osVersionInfo.dwMajorVersion == 5 && osVersionInfo.dwMinorVersion == 1)
		Str_Copy(string, "Windows XP", sizeof(string));
	else if (osVersionInfo.dwMajorVersion == 5 && osVersionInfo.dwMinorVersion == 2)
	{
		if (osVersionInfo.wProductType == VER_NT_WORKSTATION)
			Str_Copy(string, "Windows XP", sizeof(string));
		else
			Str_Copy(string, "Windows Server 2003", sizeof(string));
	}
	else if (osVersionInfo.dwMajorVersion == 6 && osVersionInfo.dwMinorVersion == 0)
	{
		if (osVersionInfo.wProductType == VER_NT_WORKSTATION)
			Str_Copy(string, "Windows Vista", sizeof(string));
		else
			Str_Copy(string, "Windows Server 2008", sizeof(string));
	}
	else if (osVersionInfo.dwMajorVersion == 6 && osVersionInfo.dwMinorVersion == 1)
	{
		if (osVersionInfo.wProductType == VER_NT_WORKSTATION)
			Str_Copy(string, "Windows 7", sizeof(string));
		else
			Str_Copy(string, "Windows Server 2008 R2", sizeof(string));
	}
	else
		Str_SPrintf(string, sizeof(string), "Windows NT %i.%i", osVersionInfo.dwMajorVersion, osVersionInfo.dwMinorVersion);

	if (osVersionInfo.szCSDVersion[0])
		Str_Append(string, Str_VarArgs(" (%s)", osVersionInfo.szCSDVersion), sizeof(string));

	Com_Printf("OS: %s\n", string);
	sys_osVersion = CVar_Register("sys_osVersion", string, CVAR_STRING, CVAR_READONLY, "OS version", 0, 0);

	// Detect processor
	Win_DetectProcessor();

	Com_Printf("CPU: %s\n", winMain.cpuString);
	sys_processor = CVar_Register("sys_processor", winMain.cpuString, CVAR_STRING, CVAR_READONLY, "CPU string", 0, 0);

	// Get system memory
	memoryStatus.dwLength = sizeof(MEMORYSTATUSEX);

	if (!GlobalMemoryStatusEx(&memoryStatus))
		Com_Error(true, "Couldn't get memory status");

	Str_SPrintf(string, sizeof(string), "%I64u MB", memoryStatus.ullTotalPhys >> 20);

	Com_Printf("RAM: %s\n", string);
	sys_memory = CVar_Register("sys_memory", string, CVAR_STRING, CVAR_READONLY, "System memory", 0, 0);

	// Get video card
	Win_GetVideoCard(string, sizeof(string));

	Com_Printf("Video: %s\n", string);
	sys_videoCard = CVar_Register("sys_videoCard", string, CVAR_STRING, CVAR_READONLY, "Video card", 0, 0);

	// Get sound card
	Win_GetSoundCard(string, sizeof(string));

	Com_Printf("Sound: %s\n", string);
	sys_soundCard = CVar_Register("sys_soundCard", string, CVAR_STRING, CVAR_READONLY, "Sound card", 0, 0);

	// Get user name
	length = sizeof(string);

	if (!GetUserName(string, (LPDWORD)&length))
		Str_Copy(string, "", sizeof(string));

	sys_userName = CVar_Register("sys_userName", string, CVAR_STRING, CVAR_READONLY, "User name", 0, 0);

	// Initialize multimedia timer
	timeBeginPeriod(1);

	// Initialize high-resolution performance timer
	if (!QueryPerformanceFrequency((LARGE_INTEGER *)&winMain.ticksPerSecond))
		Com_Error(ERR_FATAL, "No high-resolution performance timer available");

	// Get the current directory
	if (!GetCurrentDirectory(sizeof(winMain.currentDirectory), winMain.currentDirectory))
		Com_Error(ERR_FATAL, "Couldn't get current directory");

	Com_Printf("Working directory: %s\n", winMain.currentDirectory);

	// Create memory pools
	winMain.systemMemoryPool = Mem_RegisterPool("System");

	Com_Printf("---------------------------------------\n");
}

/*
 ==================
 Win_ShutdownSystem
 ==================
*/
void Win_ShutdownSystem ()
{
	// Shutdown multimedia timer
	timeEndPeriod(1);
}


/*
 ==============================================================================

    MESSAGE LOOP

 ==============================================================================
*/


/*
 ==================
 Win_GetEvents
 ==================
*/
int Win_GetEvents ()
{
	MSG		msg;
	char	*cmd;

	// If the light editor is active, pump its message loop
	if (winMain.hWndLightEditor)
	{
		while (PeekMessage(&msg, winMain.hWndLightEditor, 0, 0, PM_NOREMOVE)){
			if (!GetMessage(&msg, winMain.hWndLightEditor, 0, 0))
				Win_Exit();

			if (IsDialogMessage(winMain.hWndLightEditor, &msg))
				continue;

			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
	}

	// Pump the message loop for other windows
	while (PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)){
		if (!GetMessage(&msg, NULL, 0, 0))
			Win_Exit();

		// Special case for the editor window
		if (winMain.hWndEditor)
		{
			if (IsDialogMessage(winMain.hWndEditor, &msg))
				continue;
		}

		// Translate and dispatch the message
		winMain.msgTime = msg.time;

		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	// Check for console commands
	cmd = Win_ConsoleInput();
	if (cmd)
	{
		Cbuf_AddText(cmd);
		Cbuf_AddText("\n");
	}

	// Return the current time
	return timeGetTime();
}


/*
 ==============================================================================

    MAIN APPLICATION ENTRY AND EXIT POINT

 ==============================================================================
*/


/*
 ==================
 WinMain
 ==================
*/
int CALLBACK WinMain (__in HINSTANCE hInstance, __in HINSTANCE hPrevInstance, __in LPSTR lpCmdLine, __in int nCmdShow)
{
	// Previous instances do not exist in Win32
	if (hPrevInstance)
		return FALSE;

	winMain.hInstance = hInstance;

	// No abort/retry/fail errors
	SetErrorMode(SEM_FAILCRITICALERRORS);

	// Placeholder for additional code related to splash/console displaying...
	Win_CreateConsole();

	// Initialize all the subsystems
	Com_Init(lpCmdLine);

	// Main message loop
	while (1){
		// Don't run at full speed unless needed
		if (winMain.isMinimized || com_dedicated->integerValue)
			Sleep(10);

		// Run a frame
		Com_Framework();
	}

	// Never gets here
	return TRUE;
}

/*
 ==================
 Win_Exit
 ==================
*/
void Win_Exit ()
{
	// Shutdown all the subsystems
	Com_Shutdown();

	// Destroy the system console
	Win_ShutdownConsole();

	// Exit the process
	ExitProcess(0);
}